#pragma once

#include <any>

#include <google/protobuf/message.h>
#include <google/protobuf/util/json_util.h>

#include "_socket.h"
#include "binary.h"

static const int kHeaderLen = 4;
static const int kProtoLen = 2;
static const int MAX_PACK_SIZE = 1024 * 1024 *1024;


class TcpConn {
public:
    explicit TcpConn(_Socket* s) : s_(s) {};

    int Read(char* data, int len) {
        std::unique_lock<std::mutex> lc(readMu_);
        return s_->Read(data, len);
    }

    int Write(const char* data, int len) {
        std::unique_lock<std::mutex> lc(writeMu_);
        return s_->Write(data, len);
    }

    void SetProto(int v) {
        proto_ = v;
    }

    int Proto() {
        return proto_;
    }

    SOCKET Socket() {
        return s_->s;
    }

    ~TcpConn() {
        delete s_;
    }

    int Send(int16_t proto, const char *data, uint32_t size) {
        static char *pkg = new char[1024 * 1024 * 10];
        Binary::LittleEndian::PutUint32(pkg, size);
        Binary::LittleEndian::PutInt16(pkg + kHeaderLen, proto);
        memcpy(pkg + kHeaderLen + kProtoLen, data, size);
        return Write(pkg, kHeaderLen + kProtoLen + size);
    }

    int Read(int16_t *proto, char *data, int *size) {
        char header[kHeaderLen + 1];
        int ret = Read(header, kHeaderLen);
        if (ret != 0) {
            return ret;
        }

        char protob[kProtoLen + 1];
        ret = Read(protob, kProtoLen);
        if (ret != 0) {
            return ret;
        }

        const int bodyLen = Binary::LittleEndian::Uint32(header);
        ret = Read(data, bodyLen);
        if (ret != 0) {
            return ret;
        }

        *proto = Binary::LittleEndian::Int16(protob);
        *size = bodyLen;

        return 0;
    }

    int Send(int16_t proto, const google::protobuf::Message &msg) {
        static char *data = new char[MAX_PACK_SIZE];
        msg.SerializeToArray(data, MAX_PACK_SIZE);
        std::string json;
        if(google::protobuf::util::MessageToJsonString(msg, &json) == google::protobuf::util::Status::OK) {
            std::cout << "send " << typeid(msg).name() << " " << json << std::endl;
        }
        return Send(proto, data, static_cast<uint32_t>(msg.ByteSizeLong()));
    }

    void Set(const std::string &key, const std::any &value) {
        values_[key] = value;
    }

    template <class T>
    T Get(const std::string &key) {
        return std::any_cast<T>(values_[key]);
    }

private:
    _Socket* s_;
    std::mutex readMu_;
    std::mutex writeMu_;
    int proto_;
    std::unordered_map<std::string, std::any> values_;
};


template<class T>
using HandlerFunc = std::function<void(TcpConn *, const T &)>;

using HandleProtobufFunc = std::function<void(TcpConn *, const google::protobuf::Message &)>;